package com.jhgsys.internal.common.aspect;

import com.jhgsys.internal.common.annotation.Limit;
import com.jhgsys.internal.common.entity.LimitType;
import com.jhgsys.internal.common.exception.LimitAccessException;
import com.jhgsys.internal.common.utils.IPUtil;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;


/**
 * 接口限流
 *
 * @author MrBird
 */
@Slf4j
@Aspect
@Component
public class LimitAspect {

    private final CacheManager cacheManager;

    @Autowired
    public LimitAspect(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Pointcut("@annotation(com.jhgsys.internal.common.annotation.Limit)")
    public void pointcut() {
        // do nothing
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Limit limitAnnotation = method.getAnnotation(Limit.class);
        LimitType limitType = limitAnnotation.limitType();
        String name = limitAnnotation.name();
        String key;
        String ip = IPUtil.getIpAddr(request);
        int limitPeriod = limitAnnotation.period();
        int limitCount = limitAnnotation.count();
        switch (limitType) {
            case IP:
                key = ip;
                break;
            case CUSTOMER:
                key = limitAnnotation.key();
                break;
            default:
                key = StringUtils.upperCase(method.getName());
        }
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix() + "_", key, ip));
        Cache limitsCache = cacheManager.getCache("limits");
        String key0 = keys.get(0);
        Element value = limitsCache.get(key0);
        int count = 1;
        if (value == null) {
            Element element = new Element(key0, count);
            element.setTimeToLive(limitPeriod);
            limitsCache.put(element);
        } else {
            count = (Integer) value.getObjectValue();
            if (count <= limitCount) {
                count++;
                Element element = new Element(key0, count, value.getVersion(), value.getCreationTime(),
                        value.getLastAccessTime(), value.getLastUpdateTime(), value.getHitCount());
                element.setTimeToLive(value.getTimeToLive());
                limitsCache.replace(element);
            }
        }
        log.info("IP:{} 第 {} 次访问key为 {}，描述为 [{}] 的接口", ip, count, keys, name);
        if (count <= limitCount) {
            return point.proceed();
        } else {
            throw new LimitAccessException("接口访问超出频率限制");
        }
    }
}
